package com.felhr.usbserial;

import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicBoolean;

import android.hardware.usb.UsbAccessory;
import android.hardware.usb.UsbConstants;
import android.hardware.usb.UsbDevice;
import android.hardware.usb.UsbEndpoint;
import android.hardware.usb.UsbInterface;
import android.hardware.usb.UsbManager;
import android.os.ParcelFileDescriptor;

public class UsbSerialDevice
{
    private static final String CLASS_ID = UsbSerialDevice.class.getSimpleName();

    public UsbAccessory USBaccessory;
    public UsbManager USBmanager;

    protected static final int USB_TIMEOUT = 5000;

    private byte []	writeusbdata;
    protected ReadThread readThread;
    public FileInputStream inputstream = null;
    public FileOutputStream outputstream = null;
    public ParcelFileDescriptor filedescriptor = null;

    // Endpoints for synchronous read and write operations
    private UsbEndpoint inEndpoint;
    private UsbEndpoint outEndpoint;

    protected boolean asyncMode;


    public UsbSerialDevice(UsbAccessory accessory, UsbManager usbmanager)
    {
        this.USBaccessory = accessory;
        this.USBmanager = usbmanager;
        this.asyncMode = true;

        writeusbdata = new byte[256];
    }

    // Common Usb Serial Operations (I/O Asynchronous)
    public boolean open()
    {
        filedescriptor = USBmanager.openAccessory(USBaccessory);
        if(filedescriptor != null){
            FileDescriptor fd = filedescriptor.getFileDescriptor();

            inputstream = new FileInputStream(fd);
            outputstream = new FileOutputStream(fd);
			/*check if any of them are null*/
            if(inputstream == null || outputstream==null){
                return false;
            }
            readThread = new ReadThread();
            readThread.start();
            return true;
        }else{
            return false;
        }
    }

    /*method to send on USB*/
    private void SendPacket(int numBytes)
    {
        try {
            if(outputstream != null){
                outputstream.write(writeusbdata, 0,numBytes);
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /*write data*/
    public byte SendData(int numBytes, byte[] buffer)
    {
        byte status = 0x00; /*success by default*/
		/*
		 * if num bytes are more than maximum limit
		 */
        if(numBytes < 1){
			/*return the status with the error in the command*/
            return status;
        }

		/*check for maximum limit*/
        if(numBytes > 256){
            numBytes = 256;
        }

		/*prepare the packet to be sent*/
        for(int count = 0;count<numBytes;count++)
        {
            writeusbdata[count] = buffer[count];
        }

        if(numBytes != 64)
        {
            SendPacket(numBytes);
        }
        else
        {
            byte temp = writeusbdata[63];
            SendPacket(63);
            writeusbdata[0] = temp;
            SendPacket(1);
        }

        return status;
    }

    public void write(byte[] buffer)
    {
        int len = buffer.length;
        SendData(len, buffer);
    }

    public int read(UsbSerialInterface.UsbReadCallback mCallback)
    {
        readThread.setCallback(mCallback);
        return 0;
    }

    public  void close()
    {
        writeusbdata[0] = 0;  // send dummy data for instream.read going
        SendPacket(1);
        try{
            if(filedescriptor != null)
                filedescriptor.close();

        }catch (IOException e){}

        try {
            if(inputstream != null)
                inputstream.close();
        } catch(IOException e){}

        try {
            if(outputstream != null)
                outputstream.close();

        }catch(IOException e){}
		/*FIXME, add the notfication also to close the application*/

        filedescriptor = null;
        inputstream = null;
        outputstream = null;
    }

    public void SetConfig(int baud, byte dataBits, byte stopBits,
                          byte parity, byte flowControl)
    {

		/*prepare the baud rate buffer*/
        writeusbdata[0] = (byte)baud;
        writeusbdata[1] = (byte)(baud >> 8);
        writeusbdata[2] = (byte)(baud >> 16);
        writeusbdata[3] = (byte)(baud >> 24);

		/*data bits*/
        writeusbdata[4] = dataBits;
		/*stop bits*/
        writeusbdata[5] = stopBits;
		/*parity*/
        writeusbdata[6] = parity;
		/*flow control*/
        writeusbdata[7] = flowControl;

		/*send the UART configuration packet*/
        SendPacket((int)8);
    }

    // Serial port configuration
    public  void setBaudRate(int baudRate)
    {
        SetConfig(9600, (byte)8, (byte)1, (byte)0, (byte)0);
    }

    public  void setDataBits(int dataBits)
    {}

    public  void setStopBits(int stopBits)
    {}

    public  void setParity(int parity)
    {}

    public  void setFlowControl(int flowControl)
    {}

    //Debug options
    public void debug(boolean value)
    {

    }

    protected class ReadThread extends Thread
    {
        private UsbSerialInterface.UsbReadCallback callback;
        private UsbEndpoint inEndpoint;
        private AtomicBoolean working;

        public ReadThread()
        {
            working = new AtomicBoolean(true);
            this.setPriority(Thread.MAX_PRIORITY);
        }

        public void setCallback(UsbSerialInterface.UsbReadCallback callback)
        {
            this.callback = callback;
        }

        @Override
        public void run()
        {
            byte[] dataReceived = null;
            byte[] usbdata = new byte[1024];
            int numberBytes = 0;

            while(working.get())
            {
                try
                {
                    if(inputstream != null)
                    {
                        numberBytes = inputstream.read(usbdata, 0, 1024);
                    }
                }
                catch (IOException e){e.printStackTrace();}

                if(numberBytes > 0)
                {
                    dataReceived = Arrays.copyOfRange(usbdata, 0, numberBytes);
                    onReceivedData(dataReceived);
                }
            }
        }

        public void setUsbEndpoint(UsbEndpoint inEndpoint)
        {
            this.inEndpoint = inEndpoint;
        }

        public void stopReadThread()
        {
            working.set(false);
        }

        private void onReceivedData(byte[] data)
        {
            if(callback != null)
                callback.onReceivedData(data);
        }
    }

    /*
     * Kill workingThread; This must be called when closing a device
     */
    protected void killWorkingThread()
    {
        readThread.stopReadThread();
        readThread = null;
    }

    /*
     * Restart workingThread if it has been killed before
     */
    protected void restartWorkingThread()
    {
        readThread = new ReadThread();
        readThread.start();
        while(!readThread.isAlive()){} // Busy waiting
    }
}
